package com.fyl.validation;

import com.fyl.validation.anno.Validation;
import com.fyl.validation.anno.ValidationRule;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;

public abstract class AbstractValidator {

    public abstract <T> T getBean(Class<T> clazz);

    public List<String> validate(Object param) throws Exception {
        List<String> messages = new ArrayList<>();
        if (!needToCheck(param)) {
            return messages;
        }
        if (param instanceof Collection<?>) {
            Collection<?> params = (Collection<?>) param;
            if (!params.isEmpty()) {
                for (Object element : params) {
                    messages.addAll(validate(element));
                }
            }
            return messages;
        }

        Class<?> clazz = param.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Object value = field.get(param);

            String message = null;
            Validation validation = field.getAnnotation(Validation.class);
            if (null != validation) {
                Validation.Rule[] rules = validation.rules();
                message = doValidate(param, value, rules);
            }
            if (StringUtils.isBlank(message)) {
                ValidationRule[] rules = field.getAnnotationsByType(ValidationRule.class);
                message = doValidate(param, value, rules);
            }
            if (StringUtils.isNotBlank(message)) {
                messages.add(message);
            }

            // value 递归检查
            if (needToCheck(value)) {
                messages.addAll(validate(value));
            }
        }
        return messages;
    }


    private String doValidate(Object param, Object value, Validation.Rule... rules) throws Exception {
        for (Validation.Rule anno : rules) {

            RuleType type = anno.type();
            String rule = anno.value();
            String message = anno.message();

            if (!validateRule(param, value, type, rule)) {
                return message;
            }
        }
        return null;
    }

    private String doValidate(Object param, Object value, ValidationRule... rules) throws Exception {
        for (ValidationRule anno : rules) {

            RuleType type = anno.type();
            String rule = anno.value();
            String message = anno.message();

            if (!validateRule(param, value, type, rule)) {
                return message;
            }
        }
        return null;
    }


    private boolean validateRule(Object param, Object value, RuleType type, String rule) throws Exception {
        switch (type) {
            case REQUIRED:
                if ("true".equals(rule) && isEmpty(value)) {
                    return false;
                }
                break;
            case REGULAR:
                if (StringUtils.isNotBlank(rule) &&
                        !isEmpty(value) &&
                        !Pattern.matches(rule, String.valueOf(value))) {
                    return false;
                }
                break;
            case CUSTOM:
                if (!executeMethod(rule, value, param)) {
                    return false;
                }
                break;
        }
        return true;
    }

    /**
     * 判断类型是否是需要检查
     * param 三种情况不需要检查
     * 1. 为空
     * 2. 是 MultipartFile, HttpServlet 类型
     * 3. 是 java 原生类型(不是Collection的)
     *
     * @param param 参数
     * @return 是否需要检查
     */
    private boolean needToCheck(Object param) {
        return null != param &&
                !(param instanceof MultipartFile || param instanceof MultipartFile[]) &&
                !(param instanceof HttpServletRequest || param instanceof HttpServletResponse) &&
                (!isJavaClass(param.getClass()) || param instanceof Collection<?>);
    }

    private boolean isJavaClass(Class<?> clz) {
        return clz != null && clz.getClassLoader() == null;
    }

    private boolean executeMethod(String method, Object field, Object arg) throws Exception {
        if (StringUtils.isBlank(method) || field == null) {
            return true;
        }
        String className = method.substring(0, method.lastIndexOf("."));
        String methodName = method.substring(method.lastIndexOf(".") + 1);

        Class<?> cls = Class.forName(className);
        Object bean;
        try {
            bean = getBean(cls);
        } catch (Exception e) {
            bean = cls.newInstance();
        }

        try {
            Method m2 = cls.getDeclaredMethod(methodName, field.getClass());
            return (boolean) m2.invoke(bean, field);
        } catch (NoSuchMethodException e) {
            Method m3 = cls.getDeclaredMethod(methodName, field.getClass(), arg.getClass());
            return (boolean) m3.invoke(bean, field, arg);
        }
    }

    private boolean isEmpty(Object o) {
        if (o instanceof String) {
            return StringUtils.isBlank((String) o);
        } else if (o instanceof Collection) {
            Collection<?> collection = (Collection<?>) o;
            return collection.isEmpty();
        }
        return null == o;
    }

}
